/*
 * Copyright 2009 Zero Separation
 *
 *     This file is part of PDSUtilities.
 *
 *  PDSUtilities is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  PDSUtilities is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with PDSUtilities.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.zero_separation.pds.utilities.indexingmaps;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedReference;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class is used to wrap any field in a class which needs to be accessed
 * by a ScalableIndexingMap. The get() and set() methods should be used to
 * modify and access the contained value. Once an IndexableField is placed in
 * an object and the object indexed then this object must not be modified
 * except by using the set method provided.
 *
 * @param <T> The type of field which is being wrapped to allow indexing.
 */
public abstract class Indexable<T> implements Serializable {
    /** The version of the serialized form of this class. */
    private static final long serialVersionUID = 1L;
    
    private final BigInteger parentId;
    private final List<ManagedReference<FieldChangeListener<T>>> listeners =
            new ArrayList<ManagedReference<FieldChangeListener<T>>>();

    public Indexable(BigInteger parentId) {
        this.parentId = parentId;
    }

    /**
     * Returns the contained field value.
     *
     * @return The contained value.
     */
    abstract public T get();

    /**
     * Modifies the contained field value.
     *
     * @param field
     * @throws com.zeroseparation.testserver.indexedmaps.mk2.ScalableIndexingMap.KeyAlreadyInIndexException
     */
    abstract public void set(final T field) throws KeyAlreadyInIndexException;

    /**
     * Modifies the contained field value.
     *
     * @param field
     * @throws com.zeroseparation.testserver.indexedmaps.mk2.ScalableIndexingMap.KeyAlreadyInIndexException
     */
    protected void fireFieldChanged(final T field) throws KeyAlreadyInIndexException {

        // Do nothing if we have not changed.
        if (fieldsEqual(get(), field))
            return;

        // Check that the value is not in any indexes before starting to make
        // the change to ensure database consistency if we abort.
        for (ManagedReference<FieldChangeListener<T>> listenerRef: listeners)
            if (!listenerRef.get().validInsertion(field)) {
                Logger.getLogger(IndexableField.class.getName()).log(Level.FINEST, "Attempting to change value from "+get()+" to "+field+" which is already present in an index.");
                throw new KeyAlreadyInIndexException("Cannot change value from "+get()+" to "+field+" as "+field+" already present in an Index");
            }

        for (ManagedReference<FieldChangeListener<T>> listenerRef: listeners)
            listenerRef.get().fieldChanged(parentId, get(), field);
    }

    protected boolean fieldsEqual(T field1, T field2) {
        return (field1 == field2) || (field1 != null && field1.equals(field2));
    }

    @Override
    public String toString() {
        return get().toString();
    }

    void addListener(FieldChangeListener<T> listener) {
        listeners.add(AppContext.getDataManager().createReference(listener));
    }

    void removeListener(FieldChangeListener<T> listener) {
        listeners.remove(AppContext.getDataManager().createReference(listener));
    }

}
